﻿#include "framelesswindow.h"
#include <QApplication>
#include <QPoint>
#include <QSize>
#ifdef Q_OS_WIN

// #    include <WinUser.h>
// #    include <dwmapi.h>
// #    include <gdiplus.h>
#    include <objidl.h>   // Fixes error C2504: 'IUnknown' : base class undefined
#    include <windows.h>
#    include <windowsx.h>
#    pragma comment(lib, "dwmapi.lib")   // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea
#    pragma comment(lib, "user32.lib")

CFramelessWindow::CFramelessWindow(QWidget* parent)
	: QWidget(parent)
	, m_titlebar(Q_NULLPTR)
	, m_borderWidth(5)
	, m_bJustMaximized(false)
	, m_bResizeable(true)
{
	setWindowFlags(windowFlags() | Qt::Window);

	setResizeable(m_bResizeable);
}

void CFramelessWindow::setResizeable(bool resizeable)
{
	bool visible = isVisible();
	m_bResizeable = resizeable;

	//        setWindowFlag(Qt::WindowMaximizeButtonHint);

	// 此行代码可以带回Aero效果，同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏
	//
	// this line will get titlebar/thick frame/Aero back, which is exactly what we want
	// we will get rid of titlebar and thick frame again in nativeEvent() later
	HWND hwnd = (HWND)this->winId();
	DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
	::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION | WS_MINIMIZEBOX);

	const MARGINS shadow = { 1, 1, 1, 1 };
	DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);
}

void CFramelessWindow::setResizeableAreaWidth(int width)
{
	if (1 > width) width = 1;
	m_borderWidth = width;
}

void CFramelessWindow::setTitleBar(QWidget* titlebar)
{
	m_titlebar = titlebar;
	if (!titlebar) return;
	connect(titlebar, SIGNAL(destroyed(QObject*)), this, SLOT(onTitleBarDestroyed()));
}

void CFramelessWindow::onTitleBarDestroyed()
{
	if (m_titlebar == QObject::sender()) {
		m_titlebar = Q_NULLPTR;
	}
}

void CFramelessWindow::addIgnoreWidget(QWidget* widget)
{
	if (!widget) return;
	if (m_whiteList.contains(widget)) return;
	m_whiteList.append(widget);
}

bool CFramelessWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
{
	MSG* msg = reinterpret_cast<MSG*>(message);

	switch (msg->message) {
	case WM_NCCALCSIZE:
	{
		NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
		if (params.rgrc[0].top != 0) params.rgrc[0].top -= 1;

		//this kills the window frame and title bar we added with WS_THICKFRAME and WS_CAPTION
		*result = WVR_REDRAW;
		return true;
	}
	case WM_NCHITTEST:
	{
		const LONG border_width = m_borderWidth;
		RECT winrect;
		GetWindowRect(HWND(winId()), &winrect);

		long x = GET_X_LPARAM(msg->lParam);
		long y = GET_Y_LPARAM(msg->lParam);

		if (m_bResizeable) {
			bool resizeWidth = minimumWidth() != maximumWidth();
			bool resizeHeight = minimumHeight() != maximumHeight();

			if (resizeWidth) {
				// left border
				if (x >= winrect.left && x < winrect.left + border_width) {
					*result = HTLEFT;
				}
				// right border
				if (x < winrect.right && x >= winrect.right - border_width) {
					*result = HTRIGHT;
				}
			}
			if (resizeHeight) {
				// bottom border
				if (y < winrect.bottom && y >= winrect.bottom - border_width) {
					*result = HTBOTTOM;
				}
				// top border
				if (y >= winrect.top && y < winrect.top + border_width) {
					*result = HTTOP;
				}
			}
			if (resizeWidth && resizeHeight) {
				// bottom left corner
				if (x >= winrect.left && x < winrect.left + border_width && y < winrect.bottom && y >= winrect.bottom - border_width) {
					*result = HTBOTTOMLEFT;
				}
				// bottom right corner
				if (x < winrect.right && x >= winrect.right - border_width && y < winrect.bottom && y >= winrect.bottom - border_width) {
					*result = HTBOTTOMRIGHT;
				}
				// top left corner
				if (x >= winrect.left && x < winrect.left + border_width && y >= winrect.top && y < winrect.top + border_width) {
					*result = HTTOPLEFT;
				}
				// top right corner
				if (x < winrect.right && x >= winrect.right - border_width && y >= winrect.top && y < winrect.top + border_width) {
					*result = HTTOPRIGHT;
				}
			}
		}
		if (0 != *result) return true;

		//*result still equals 0, that means the cursor locate OUTSIDE the frame area
		// but it may locate in titlebar area
		if (!m_titlebar) return false;

		// support highdpi
		double dpr = this->devicePixelRatioF();
		QPoint pos = m_titlebar->mapFromGlobal(QPoint(x / dpr, y / dpr));

		if (!m_titlebar->rect().contains(pos)) return false;
		QWidget* child = m_titlebar->childAt(pos);
		if (!child) {
			*result = HTCAPTION;
			return true;
		}
		else {
			if (m_whiteList.contains(child)) {
				*result = HTCAPTION;
				return true;
			}
		}
		return false;
	}   // end case WM_NCHITTEST
	case WM_GETMINMAXINFO:
	{
		if (::IsZoomed(msg->hwnd)) {
			RECT frame = { 0, 0, 0, 0 };
			AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);

			// record frame area data
			double dpr = this->devicePixelRatioF();

			m_frames.setLeft(abs(frame.left) / dpr + 0.5);
			m_frames.setTop(abs(frame.bottom) / dpr + 0.5);
			m_frames.setRight(abs(frame.right) / dpr + 0.5);
			m_frames.setBottom(abs(frame.bottom) / dpr + 0.5);

			QWidget::setContentsMargins(m_frames.left() + m_margins.left(), m_frames.top() + m_margins.top(), m_frames.right() + m_margins.right(), m_frames.bottom() + m_margins.bottom());
			m_bJustMaximized = true;
		}
		else {
			if (m_bJustMaximized) {
				QWidget::setContentsMargins(m_margins);
				m_frames = QMargins();
				m_bJustMaximized = false;
			}
		}
		return false;
	}
	default: return QWidget::nativeEvent(eventType, message, result);
	}
}

#endif   // Q_OS_WIN